home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 4 / Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso / Development / Source / MacStarter (THINK C 5.0⁄6.0) / other applicationProcs files / kaleidoSketch.c next >
Text File  |  1994-01-25  |  18KB  |  467 lines

  1. /* This file demonstrates some slightly sophisticated drawing, 
  2.    as well as basic use of files in MacStarter.
  3.    To use this file, you should add it to the MacStarter.π project
  4.    and delete the current applicationProcs file.  The file
  5.    inputBoxes.c must also be present in the MacStarter.π project
  6.    window.
  7.        When using this program, the user draws in a window using the
  8.    mouse.  The curve traced by the mouse is drawn, along with its
  9.    seven "reflections", giving a pretty(?), symmetric pattern.  Sketches
  10.    can be saved to files and reloaded.
  11.        All places where this file has been modified (from the 
  12.    original applicationProcs.c) are commented with comments
  13.    that begin with //
  14. */
  15.  
  16.  
  17. #include "globals-MacStarter.h"
  18. #include "inputBoxes.h"
  19.  
  20. long gEventWaitTime = 100000;
  21.  
  22. MenuHandle editMenu, fileMenu;
  23.  
  24. void InitApplication(void);
  25. void UpdateMenus(void);
  26. void DoEditMenu(int itemNum);
  27. void DoFileMenu(int itemNum, int* done);
  28. void DoOtherMenu(int menuID, int itemNum);
  29. void ApplicationIdle(void);
  30. void CleanUpApplication(void);
  31. void AboutBox(void);
  32. void DoNewCommand(void);
  33.  
  34.  
  35. // define a symbolic constant specifying the maximum number of points 
  36. // on the curves drawn by the user in a single window.
  37. #define maxPoints 1000
  38.  
  39. short WindowCt = 0;  // to keep track of the number of windows opened
  40.                      // by the program; see function DoNewCommand below.
  41.  
  42. void doOpen(void);  // Declare a function to carry out Open Command
  43.  
  44.  
  45. class myWindow : public xWindow {
  46.  
  47.   public:
  48.    // (Note: not all this stuff needs to be public; I am being lazy here.
  49.    //  It was easiest to make everything public when I kept getting
  50.    //  "access denied" errors, rather that improve the design.  This is
  51.    //  not so bad in a small program that doesn't have to be maintained,
  52.    //  I guess.)
  53.    
  54.    // define the data that is relevant to the contents of this particular window:   
  55.    
  56.    short ptCount;        // how many points have been drawn (0 to maxPoints)
  57.    
  58.    short allFilledUp;    // this is initialized to 0 and is set to 1 when
  59.                          //      the number of points first reaches maxPoints
  60.                          //      (It exists so that I can avoid giving the
  61.                          //       user more than one "all filled up" message
  62.                          //       per window.)
  63.                          
  64.    Point pt[maxPoints];      // A list of the points drawn by the user.  As the
  65.                              // user moves the mouse, a list of points traversed
  66.                              // is kept.  The curve that is drawn is just
  67.                              // obtained by connecting these points.
  68.                              // The special value of pt[i].h = -1 separates
  69.                              // sequences of points on different curves.
  70.                              
  71.    short winWidth,winHeight; // When the window changes size, the points in
  72.                              // array pt have to be scaled to the new size
  73.                              // these variables store the width and height
  74.                              // by which the points are curently scaled.
  75.                              // (These values are changed in function
  76.                              // adjustToNewSize, where the scaling is done.)
  77.    
  78.    void doClear(void);   // Function to respond to Clear command
  79.    
  80.    void doSave(void);    // Function to respond to Save command.
  81.                          // (Note:  I put doClear and doSave inside the
  82.                          //  window definition because they are directed at
  83.                          //  a particular window.  The commands New and
  84.                          //  Open are not directed to a particular window,
  85.                          //  so their handlers are not part of the window
  86.                          //  definition.)
  87.  
  88.    void PutLineSegment(Point pt1, Point pt2);
  89.                     // Draws a line from pt1 to pt2 along with the seven
  90.                     // possible "reflections" of the line.
  91.    
  92.    virtual void OpenInRect(Str255 title, int left, int top, int right, int bottom);   
  93.    virtual short Close(void);
  94.  
  95.    virtual void adjustToNewSize(void);
  96.    virtual void SetDefaults(void);
  97.    virtual void doKey(char ch);
  98.    virtual void doContentClick(Point localPt);
  99.    virtual void doRedraw(Rect* badRect);
  100.    virtual void doHScroll(int dh);
  101.    virtual void doVScroll(int dv);
  102.    virtual void doActivate(int active);
  103.    
  104. };
  105.  
  106.  
  107. // Function doClear is called in response to a Clear command from the 
  108. // edit menu.  It just clears the window by declaring that there are no
  109. // points for the window.  NOTE the use of ForceRedraw(0), which causes the
  110. // window to be erased and redrawn by the functions with those specific
  111. // jobs.  This is generally preferable to doing the redrawing "in place",
  112. // since that can lead to unnecessary code duplication.
  113.  
  114. void myWindow::doClear(void) {
  115.    ptCount = 0;
  116.    allFilledUp = 0;
  117.    ForceRedraw(0);
  118. }
  119.  
  120.  
  121. // Function doSave is called in response to the Save command from the
  122. // file menu.  It uses function OpenNewFile (defined in inputBoxes.c)
  123. // to select the new file's name and folder using a standard Mac new file 
  124. // dialog.  (I should probably do more error checking here but in fact,
  125. // an error is unlikely.)
  126.  
  127. void myWindow::doSave(void) {
  128.    FILE *file;
  129.    Str255 title;
  130.    short i;
  131.    GetTitle(title);
  132.    if ( file = OpenNewFile("\pSave sketch in:",title) ) {
  133.       SetTitle(title);
  134.       fprintf(file, "%hd %hd %hd\n", winWidth, winHeight, ptCount);
  135.       for (i=0; i<ptCount; i++)
  136.          fprintf(file, "%hd %hd\n", pt[i].h, pt[i].v);
  137.       fclose(file);
  138.    };
  139. }
  140.  
  141.  
  142. // Function doOpen is called in response to the Save command from the
  143. // file menu.  It used function OpenOldFile (defined in inputBoxes.c)
  144. // to allow the user to specify the file to be read. 
  145. // I do a fair amount of error-checking here because it is possible
  146. // for the user to select any text file, and if the file is not one
  147. // produced previously by this program, it will almost certainly generate
  148. // an error.
  149.  
  150. void doOpen(void) {
  151.    FILE *file;
  152.    Str255 title;
  153.    short width,height,ptCt;
  154.    short h,v;
  155.    short i;
  156.    myWindow *win;
  157.    if ( file = OpenOldFile(title) ) {
  158.       win = new myWindow;
  159.       win->Open(title);  // note that the window title is the file name
  160.                          // that is returned by OpenOldFile
  161.       if (fscanf(file,"%hd %hd %hd\n", &width, &height, &ptCt) != 3
  162.             || width <= 0 || height < 0 || ptCt > maxPoints) {
  163.          TellUser("\pSorry, an error occured while trying to read from the file.");
  164.          win->Close();
  165.          fclose(file);
  166.          return;
  167.       }
  168.       else {
  169.          win->winHeight = height;
  170.          win->winWidth = width;
  171.          win->ptCount = ptCt;
  172.       };
  173.       for (i=0; i<win->ptCount; i++) {
  174.         if (fscanf(file, "%hd %hd\n", &h, &v) != 2
  175.              || h < -1 || v < 0 || v>height || h>width ) {
  176.            TellUser("\pSorry, illegal data or end-of-file encountered while trying to read from the file.");
  177.            win->Close();
  178.            fclose(file);
  179.            return;
  180.         }
  181.         else {
  182.            win->pt[i].h = h;
  183.            win->pt[i].v = v;
  184.         }
  185.       };
  186.       fclose(file);
  187.       win->adjustToNewSize();
  188.    }
  189. }
  190.  
  191. void myWindow::SetDefaults(void) {
  192.    inherited::SetDefaults();
  193.    
  194.    features = hasGoAway + hasZoom + hasGrow;  // modify the feature list;
  195.           // I don't want any scroll bars, but I do want a close box,
  196.           // zoom box and grow box
  197.    
  198.    ptCount = 0;     // initialize window data; there are initially no points
  199.    allFilledUp = 0; // and the window is NOT allFilledUp. 
  200.    
  201.    minH = 100;  // set minimum window size when user drags "grow box"
  202.    minV = 100;
  203. }
  204.  
  205. void myWindow::OpenInRect(Str255 title, int left, int top, int right, int bottom) {
  206.    inherited::OpenInRect(title,left,top,right,bottom);
  207. }
  208.  
  209. short myWindow::Close(void) {
  210.    inherited::Close();
  211. }
  212.  
  213. void myWindow::doKey(char ch) {
  214. }
  215.  
  216.  
  217. // Function PutLineSegment is used by doRedraw and by doContentClick to
  218. // draw a line segment and its seven possible "reflections" (using horizontal,
  219. // vertical and diagonal reflection).  (I put reflections in quotes since
  220. // the diagonal "reflection" is not really a true reflection if the window
  221. // is not square.)  This function assumes that the drawing port is already
  222. // set to be this window.
  223.  
  224. void myWindow::PutLineSegment(Point pt1, Point pt2) {
  225.    short a,b,x,y;
  226.    
  227.    a = pt1.h;  // first, plot the line itself and its horizontal, 
  228.    b = pt1.v;  // vertical and combined horizontal/vertical reflection
  229.    x = pt2.h;
  230.    y = pt2.v; 
  231.    MoveTo(a,b);                     LineTo(x,y);
  232.    MoveTo(winWidth-a,b);            LineTo(winWidth-x,y);
  233.    MoveTo(winWidth-a,winHeight-b);  LineTo(winWidth-x,winHeight-y);
  234.    MoveTo(a,winHeight-b);           LineTo(x,winHeight-y);
  235.    
  236.    a = pt1.v * ((double) winWidth )/ winHeight; // next, compute the diagonal
  237.    b = pt1.h * ((double) winHeight )/ winWidth; // "reflection" of the original
  238.    x = pt2.v * ((double) winWidth )/ winHeight; // line and plot it and its
  239.    y = pt2.h * ((double) winHeight )/ winWidth; // reflections
  240.    MoveTo(a,b);                     LineTo(x,y);
  241.    MoveTo(winWidth-a,b);            LineTo(winWidth-x,y);
  242.    MoveTo(winWidth-a,winHeight-b);  LineTo(winWidth-x,winHeight-y);
  243.    MoveTo(a,winHeight-b);           LineTo(x,winHeight-y);
  244. }
  245.  
  246.  
  247. void myWindow::doContentClick(Point localPt) {
  248.    Point lastPt, nextPt;
  249.    short didSomeDrawing;
  250.    if (allFilledUp)   // drawing after allFilledUp is not allowed
  251.       return;
  252.    lastPt = localPt;    // starting point of curve
  253.    didSomeDrawing = 0;
  254.    while (StillDown()) {  // continue while the user holds down the mouse button
  255.  
  256.       GetMouse(&nextPt);  // read mouse location
  257.  
  258.       if (nextPt.h < 0)   // clip mouse location to make sure it is in window
  259.          nextPt.h = 0;
  260.       else if (nextPt.h > winWidth)
  261.          nextPt.h = winWidth;
  262.       if (nextPt.v < 0)
  263.          nextPt.v = 0;
  264.       else if (nextPt.v > winHeight)
  265.          nextPt.v = winHeight;
  266.  
  267.       if ( (nextPt.h-lastPt.h > 1)               // test if the new point is
  268.              || (nextPt.h-lastPt.h < -1)         // not too close to last point;
  269.              || (nextPt.v-lastPt.v > 1)          // this avoids storing
  270.              || (nextPt.v-lastPt.v < -1) ) {     // too many points
  271.          if (ptCount == maxPoints) {
  272.             TellUser("\pEach window can only have a certain amount of data.  This window's data space is now completely filled.  You can't do any more drawing in it.");
  273.             allFilledUp = 1;  // out of room; stop drawing
  274.             return;
  275.          };
  276.          pt[ptCount++] = lastPt;        // record a point in data array
  277.          PutLineSegment(lastPt,nextPt); // and draw the line and reflections
  278.          lastPt = nextPt;
  279.          didSomeDrawing = 1;  // remember that at least one point was recorded
  280.       };
  281.    };
  282.    
  283.    if (didSomeDrawing && ptCount < maxPoints)
  284.       pt[ptCount++].h = -1;  // if any points were stored, put a sentinel
  285.                              // in the array to separate this curve from the
  286.                              // next one the user might draw  (so that they
  287.                              // are not connected when the window is redrawn)
  288. }
  289.  
  290.  
  291. void myWindow::doRedraw(Rect* badRect){
  292.    short i;
  293.    if (ptCount > 0)      // redraw any curves entered previously by user
  294.      for (i=1; i<ptCount; i++)
  295.         if (pt[i].h != -1 && pt[i-1].h != -1)  // an h value of -1 is not
  296.            PutLineSegment(pt[i-1],pt[i]);      // actually on a curve; this is
  297.                                                // a sentinel value between curves
  298. }
  299.  
  300.  
  301. void myWindow::adjustToNewSize(void) {
  302.    Rect oldRect,newRect;
  303.    short i;
  304.    inherited::adjustToNewSize();
  305.    
  306.     // Rescale the points to fit correctly into the newly resized window;
  307.     // be careful not to scale the sentinel points with h = -1 as these
  308.     // are not really points, but are only there to separate data for
  309.     // different curves.  (NOTE:  because coordinates are integers,
  310.     // the scaling done here involves some rounding errors, which
  311.     // introduces some distortions into the image.)
  312.     
  313.    SetRect(&oldRect,0,0,winWidth,winHeight);
  314.    SetRect(&newRect,0,0,theWindow->portRect.right - 1,theWindow->portRect.bottom - 1);
  315.     // These rectangles are required for the Mac toolbox function ScalePt
  316.     // which is used to do the scaling.  oldRect is the old window rectangle,
  317.     // and newRect is the window rectangle after resizing.  (The size of the
  318.     // old rectangle has to be remembered in the window data variables
  319.     // winHeight and winWidth.)
  320.    
  321.    for (i=0; i<ptCount; i++) {  // scale the points
  322.       if (pt[i].h != -1)
  323.           ScalePt(pt+i, &oldRect, &newRect);
  324.    };
  325.    
  326.    winWidth = newRect.right;   // record current window size
  327.    winHeight = newRect.bottom;
  328.    
  329. }
  330.  
  331.  
  332. void myWindow::doHScroll(int dh) {
  333.    inherited::doHScroll(dh);
  334. }
  335.  
  336. void myWindow::doVScroll(int dv) {
  337.    inherited::doVScroll(dv);
  338. }
  339.  
  340. void myWindow::doActivate(int active) {
  341.    inherited::doActivate(active);
  342. }
  343.  
  344. void InitApplication(void) {
  345.   MenuHandle appleMenu;
  346.   fileMenu = GetMHandle(2);
  347.   editMenu = GetMHandle(3);
  348.   appleMenu = GetMHandle(1);
  349.   SetItem(appleMenu,1,"\pAbout KaleidoSketch..."); // Program name for Apple Menu
  350.   SetItem(fileMenu,1,"\pNew");  // Replaces "New Window" in file menu
  351.                                 //  (Seems better for a program that uses files.)
  352.   InsMenuItem(fileMenu, "\pSave/S;Open/O", 1);
  353.                   // Add two file-oriented commands as entries #2 and 3 in file
  354.                   // menu; (Note that you could use ResEdit to build menus.)
  355.                   // Note that the item numbers for Quit and Close command
  356.                   // are changed, which forces some changes in functions
  357.                   // UpdateMenus and DoFileMenu.
  358.   DoNewCommand();
  359. }
  360.  
  361. void UpdateMenus(void) {
  362.    short i;
  363.    WindowPtr win;
  364.    xWindow *xwin;
  365.    win = FrontWindow();
  366.    if ( win && ((WindowPeek)win)->windowKind < 0 ) {
  367.       EnableItem(editMenu,1);
  368.       for (i=3; i<7; i++)
  369.          EnableItem(editMenu,i);
  370.    }
  371.    else {
  372.       DisableItem(editMenu,1);
  373.       for (i=3; i<6; i++)         // #6 ("Clear") is handled below
  374.          DisableItem(editMenu,i);
  375.    }
  376.    if (win && xWindow::Window2XWindow(win,&xwin)) {
  377.         // the frontmost window is one belonging to this program, so I want
  378.         // to enable the commands that are window-directed, such as Close,
  379.         // and possibly Clear and Save (if the window is not empty).
  380.       EnableItem(fileMenu,4);  // Close Window Command
  381.          // The next line checks whether anything has been drawn in the front
  382.          // window.  The type-cast (myWindow*) is required to get access to
  383.          // ptCount, since the compiler knows only that xwin is an xWindow.
  384.       if ( ((myWindow*)xwin)->ptCount > 0 ) {
  385.          EnableItem(fileMenu,3);  // Save Command
  386.          EnableItem(editMenu,6);  // Clear Command
  387.       }
  388.       else {  // front window is empty; nothing to save or clear
  389.          DisableItem(fileMenu,3); 
  390.          DisableItem(editMenu,6);
  391.       }
  392.    }
  393.    else {  // No front window to close, save, or clear
  394.       DisableItem(fileMenu,3);
  395.       DisableItem(fileMenu,4);
  396.       DisableItem(editMenu,6);
  397.    }
  398. }
  399.  
  400. void DoEditMenu(int itemNum) {
  401.    // having decided to use the Clear command from the Edit menu in my
  402.    // program, I have to handle it here.  Note again the use of type-
  403.    // casting, which is necessary because Window2XWindow returns an
  404.    // xWindow, not a myWindow.
  405.    //    Note: The test in the first line should always be true; If UpdateMenus 
  406.    // is correct, the Clear command will be disabled unless it is appropriate
  407.    xWindow *xwin;
  408.    if ( itemNum == 6 && xWindow::Window2XWindow(FrontWindow(),&xwin) )
  409.       ((myWindow*)xwin)->doClear();
  410. }
  411.  
  412. void DoFileMenu(int itemNum, int* done) {
  413.    // This function has been modified to reflect the new commands (Save and
  414.    // Open) added to the file menu in InitApplication.
  415.    xWindow *win;
  416.    if (itemNum == 6)  // changed number of quit command from 4 to 6
  417.       *done = 1;
  418.    else if (itemNum == 1)
  419.       DoNewCommand();
  420.    else if (itemNum == 2)
  421.       doOpen();
  422.    else if (itemNum == 3 && xWindow::Window2XWindow(FrontWindow(),&win))
  423.       ((myWindow*)win)->doSave();
  424.    else if (itemNum == 4 && xWindow::Window2XWindow(FrontWindow(),&win))
  425.       win->Close();
  426. }
  427.  
  428. void DoOtherMenu(int menuID, int itemNum) {
  429. }
  430.  
  431. void ApplicationIdle(void) {
  432. }
  433.  
  434. void CleanUpApplication(void) {
  435. }
  436.  
  437. void AboutBox(void) {
  438.    // The strings in the call to ParamText have been modified to describe
  439.    // this program.  (Again, this is not a secure way of identifying your
  440.    // program, as noted in the comments on file applicationProcs.c.)
  441.    ParamText( "\pKaleidoSketch",
  442.               "\pDavid Eck",
  443.               "\pHobart and William Smith Colleges\rGeneva, NY  14456\rE-mail:  eck@hws.bitnet",
  444.               "\pThis program was written in THINK C to illustrate the use of a Macintosh application shell that I wrote.");
  445.    Alert(128,0L);
  446. }
  447.  
  448. void DoNewCommand(void) {
  449.    // This function has been modified to keep track of how many windows have
  450.    // been opened and to name them appropriately: Untitled 1, Untitled 2, ...
  451.    // There are some tricks here because win->open(title) requires a
  452.    // Pascal style string, but it is easiest to produce the title using
  453.    // sprintf, which makes a C style string.  This requires the conversion
  454.    // CtoPstr and the type-cast of title in the call to sprintf.
  455.    // This type-cast is required because Str255 is type (unsigned char)*
  456.    // and sprintf requires char*  (I think).
  457.    // (By the way, sprintf requires that this file include stdio.h, which is
  458.    // done indirectly through the header inputBoxes.h so it is not necessary
  459.    // to include it explcitely.)
  460.    myWindow *win;
  461.    Str255 title;
  462.    win = new myWindow;
  463.    WindowCt++;
  464.    sprintf((char*)title,"Untitled %i",WindowCt);
  465.    CtoPstr((char*)title);
  466.    win->Open(title);
  467. }